#include "stdafx.h"
#include "VertexBuffer.h"

namespace Reign
{namespace Video
{namespace D3D10
{
	#pragma region Properties
	VertexBufferTopologys VertexBuffer::Topology::get() {return topology;}
	void VertexBuffer::Topology::set(VertexBufferTopologys value)
	{
		switch (value)
		{
			case (VertexBufferTopologys::None): primitiveTopology = D3D10_PRIMITIVE_TOPOLOGY_POINTLIST; break;
			case (VertexBufferTopologys::Point): primitiveTopology = D3D10_PRIMITIVE_TOPOLOGY_POINTLIST; break;
			case (VertexBufferTopologys::Line): primitiveTopology = D3D10_PRIMITIVE_TOPOLOGY_LINELIST; break;
			case (VertexBufferTopologys::Triangle): primitiveTopology = D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST; break;
		}
		topology = value;
	}
	#pragma endregion

	#pragma region Constructors
	VertexBuffer::VertexBuffer(DisposableI^ parent, BufferLayoutDescI^ bufferLayoutDesc, BufferUsages bufferUsage, VertexBufferTopologys vertexBufferTopology)
	: VertexBufferI(parent, bufferLayoutDesc, bufferUsage)
	{
		init(parent, vertexBufferTopology, nullptr);
	}

	VertexBuffer::VertexBuffer(DisposableI^ parent, BufferLayoutDescI^ bufferLayoutDesc, BufferUsages bufferUsage, VertexBufferTopologys vertexBufferTopology, array<float>^ vertices)
	: VertexBufferI(parent, bufferLayoutDesc, bufferUsage)
	{
		init(parent, vertexBufferTopology, vertices);
	}

	void VertexBuffer::init(DisposableI^ parent, VertexBufferTopologys vertexBufferTopology, array<float>^ vertices)
	{
		null();
		try
		{
			video = parent->FindParentOrSelfWithException<Video^>();
			Topology = vertexBufferTopology;

			if (vertices) Init(vertices);
		}
		catch (Exception^ ex)
		{
			delete this;
			throw ex;
		}
	}

	VertexBuffer::~VertexBuffer()
	{
		disposeChilderen();
		if (vertexBuffer) vertexBuffer->Release();
		null();
	}

	void VertexBuffer::null()
	{
		vertexBuffer = 0;
		video = nullptr;
	}
	#pragma endregion

	#pragma region Methods
	void VertexBuffer::Init(array<float>^ vertices)
	{
		VertexBufferI::Init(vertices);
		if (vertexBuffer)
		{
			vertexBuffer->Release();
			vertexBuffer = 0;
		}

		uint bufferSize = vertexCount * vertexByteSize;
		D3D10_BUFFER_DESC desc;
		ZeroMemory(&desc, sizeof(D3D10_BUFFER_DESC));
		desc.Usage = (usage == BufferUsages::Write) ? D3D10_USAGE_DYNAMIC : D3D10_USAGE_DEFAULT;
		desc.CPUAccessFlags = (usage == BufferUsages::Write) ? D3D10_CPU_ACCESS_WRITE : 0;
		desc.ByteWidth = bufferSize;
		desc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
		desc.MiscFlags = 0;

		D3D10_SUBRESOURCE_DATA InitData;
		ZeroMemory(&InitData, sizeof(D3D10_SUBRESOURCE_DATA));
		pin_ptr<float> verticesTEMP = &vertices[0];
		InitData.pSysMem = verticesTEMP;
		ID3D10Buffer* vertexBufferTEMP;
		if (FAILED(video->Device->CreateBuffer(&desc, &InitData, &vertexBufferTEMP)))
		{
			Debug::ThrowError(L"VertexBuffer", L"Failed to create VertexBuffer");
		}
		vertexBuffer = vertexBufferTEMP;
	}

	void VertexBuffer::Update(array<float>^ vertices, int updateCount)
	{
		void* data = 0;
		vertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &data);
		pin_ptr<float> verticesTEMP = &vertices[0];
		memcpy(data, verticesTEMP, vertexByteSize * updateCount);
		vertexBuffer->Unmap();
	}

	void VertexBuffer::enable(IndexBufferI^ indexBuffer, VertexBufferI^ instanceBuffer)
	{
		video->Device->IASetPrimitiveTopology(primitiveTopology);

		if (instanceBuffer)
		{
			VertexBuffer^ ib = (VertexBuffer^)instanceBuffer;
			uint offset[2] = {0, 0};
			uint byteSizeTEMP[2] = {vertexByteSize, ib->vertexByteSize};
			ID3D10Buffer* bufferTEMP[2] = {vertexBuffer, ib->vertexBuffer};
			video->Device->IASetVertexBuffers(0, 2, &*bufferTEMP, byteSizeTEMP, offset);
		}
		else
		{
			const uint offset = 0, byteSizeTEMP = vertexByteSize;
			ID3D10Buffer* bufferTEMP = vertexBuffer;
			video->Device->IASetVertexBuffers(0, 1, &bufferTEMP, &byteSizeTEMP, &offset);
		}

		if (indexBuffer)
		{
			this->indexBuffer = (IndexBuffer^)indexBuffer;
			this->indexBuffer->enable();
		}
		else
		{
			this->indexBuffer = nullptr;
		}
	}

	void VertexBuffer::Enable()
	{
		enable(nullptr, nullptr);
	}

	void VertexBuffer::Enable(IndexBufferI^ indexBuffer)
	{
		enable(indexBuffer, nullptr);
	}

	void VertexBuffer::Enable(VertexBufferI^ instanceBuffer)
	{
		enable(nullptr, instanceBuffer);
	}

	void VertexBuffer::Enable(IndexBufferI^ indexBuffer, VertexBufferI^ instanceBuffer)
	{
		enable(indexBuffer, instanceBuffer);
	}

	void VertexBuffer::Draw()
	{
		if (indexBuffer == nullptr) video->Device->Draw(vertexCount, 0);
		else video->Device->DrawIndexed(indexBuffer->IndexCount, 0, 0);
	}

	void VertexBuffer::Draw(int drawCount)
	{
		if (indexBuffer == nullptr) video->Device->Draw(drawCount, 0);
		else video->Device->DrawIndexed(drawCount, 0, 0);
	}

	void VertexBuffer::DrawInstanced(int drawCount)
	{
		if (indexBuffer == nullptr) video->Device->DrawInstanced(vertexCount, drawCount, 0, 0);
		else video->Device->DrawIndexedInstanced(indexBuffer->IndexCount, drawCount, 0, 0, 0);
	}

	void VertexBuffer::DrawInstancedClassic(int drawCount, int meshVertexCount, int meshIndexCount)
	{
		if (indexBuffer == nullptr) video->Device->Draw(drawCount * meshVertexCount, 0);
		else video->Device->DrawIndexed(drawCount * meshIndexCount, 0, 0);
	}
	#pragma endregion
}
}
}